{mariobox}: des APIs {plumber} à toute épreuve

Rencontres R 2023 - Avignon

Antoine Languillaume

Dessine moi une API

API == Application Programming Interface


Ce qui n’est vraiment pas très clair

Une API, permet à un ordinateur de demander une information à un autre ordinateur, par internet.

Ok mais ça ressemble à quoi ?

Simplement à une url HTTP

https://calendrier.api.gouv.fr/jours-feries/metropole/2024

Tous les jours fériés de 2024

https://api.punkapi.com/v2/beers?food=banana

Toutes les bières Brewdog qui s’allient bien avec la banane

https://api.github.com/users/thinkr-open

Toutes les infos publiques sur le compte github de ThinkR

APIs: lingua franca interneti

Côté client il nous suffit de requêter l’url avec le langage de notre choix.

# R
httr2::request("https://calendrier.api.gouv.fr/jours-feries/metropole/2024")
// JavaScript
fetch("https://calendrier.api.gouv.fr/jours-feries/metropole/2024)
# Python
requests.get("https://calendrier.api.gouv.fr/jours-feries/metropole/2024")
// Go
resp, err := http.Get("https://calendrier.api.gouv.fr/jours-feries/metropole/2024")

Peu importe le langage ayant servi à l’implémentation côté serveur.

Et pourquoi pas R ?

Il n’a pas fallu attendre 2023 pour s’en rendre compte

Le vilain petit {plumber}

{plumber} c’est cool pour :

L’interopérabilité

  • Une API écrite en R peut s’intégrer dans un système écrit en Java, Python, TurboPascal…

La scalabilité

  • Pas de websocket

  • Pas d’état, n’importe quel client peut requêter n’importe quel serveur

{plumber} c’est cool mais…


#* Echo back the input
#* @param msg The message to echo
#* @get /echo
function(msg="") {
  list(msg = paste0("The message is: '", msg, "'"))
}


Yet Another: #, #’, #* …

Un nouvel épisode de packagite aigüe

Tout est package

Un package c’est :

  • de la documentation
  • des tests
  • une gestion intégrée des dépendances

Plusieurs crises de meta-packagite aigüe par le passé

Ainsi naquit {mariobox}


En route !

  1. Installer {mariobox}
pak::pak("thinkr-open/mariobox")
# ou
remotes::install_github("thinkr-open/mariobox")


  1. Créer une nouvelle API packagée
mariobox::create_mariobox(
 path = "mon.api"
)

La structure de base

> fs::dir_tree("mon.api/")
mon.api/
├── DESCRIPTION
├── NAMESPACE
├── R
   ├── get_health.R
   └── run_plumber.R
├── dev
   └── run_dev.R
├── inst
   └── mariobox.yml # Le coeur d'une API {mariobox}
├── man
   ├── get_health.Rd
   └── run_api.Rd
└── tests
    ├── testthat
       ├── test-health.R
       └── test-run_plumber.R
    └── testthat.R

Le coeur d’une API {mariobox}

$ cat inst/mariobox.yml 
metadata:
  title: mariobox API
handles:
  health_get:
    methods: GET
    path: /health
    handler: get_health

Tester {mon.api}

> source("dev/run_dev.R", echo = TRUE)
[..]
Running plumber API at http://127.0.0.1:19483
Running swagger Docs at http://127.0.0.1:19483/__docs__/
  • Depuis un terminal
> httr::GET("http://127.0.0.1:19483/health")
Response [http://127.0.0.1:19483/health]
  Date: 2023-06-19 10:39
  Status: 200
  Content-Type: application/json
  Size: 6 B

> httr::GET("http://127.0.0.1:19483/health") |> httr::content()
[[1]]
[1] "ok"
  • Depuis votre navigateur: http://127.0.0.1:19483/__docs__/

Ajouter un endpoint

mariobox::add_endpoint(
  name = "text",
  method = "GET"
)

Va :

  • Mettre à jour /inst/mariobox.yml
  • Créer un fichier R/get_text.R
  • Créer un test tests/testthat/test-get_text.R

La méthode {mariobox}

#' GET text
#'
#' @param req,res HTTP objects
#'
#' @export
get_text <- function(req, res) {
  mariobox::mario_log(
    method = "GET",
    name = "text"
  )
  get_text_f()
}

#' GET text internal
#'
#' @noRd
get_text_f <- function() {
  return("Coucou !")
}


METHOD_NAME()

  • get_text()
  • HTTP


METHOD_NAME_f()

  • get_text_f()
  • logique métier


“Separation of concerns”

Séparation des préoccupations

Re-tester {mon.api}

> source("dev/run_dev.R", echo = TRUE)
[..]
Running plumber API at http://127.0.0.1:19483
Running swagger Docs at http://127.0.0.1:19483/__docs__/
  • Depuis un terminal
> httr::GET("http://127.0.0.1:19483/text")
Response [http://127.0.0.1:19483/text]
  Date: 2023-06-19 10:39
  Status: 200
  Content-Type: application/json
  Size: 6 B

> httr::GET("http://127.0.0.1:19483/text") |> httr::content()
[[1]]
[1] "Coucou !"

En bref

Des APIs avec mariobox c’est…

  • Une méthode scalable pour diffuser le résultats de vos calculs R
  • Sans avoir à réfléchir à comment structurer votre code
  • Ni à apprendre de nouvelle syntaxe
  • et qui peut s’intégrer dans n’importe quel SI

https://github.com/ThinkR-open/mariobox

Merci !